use core::fmt;
// use byteorder::{ByteOrder, NetworkEndian};

use crate::{Error, Result};

pub use super::EthernetProtocol as Protocol;

/// A read/write wrapper around an Address Resolution Protocol packet buffer.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
    buffer: T,
}

mod field {
    #![allow(non_snake_case)]

    use crate::wire::field::*;

    pub const IDENT: Field = 0..8;
    pub const PWLEN: Field = 8..10;
    pub const PWCON: Field = 10..42;
}

impl<T: AsRef<[u8]>> Packet<T> {
    /// Imbue a raw octet buffer with ARP packet structure.
    pub fn new_unchecked(buffer: T) -> Packet<T> {
        Packet { buffer }
    }

    /// Shorthand for a combination of [new_unchecked] and [check_len].
    ///
    /// [new_unchecked]: #method.new_unchecked
    /// [check_len]: #method.check_len
    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
        let packet = Self::new_unchecked(buffer);
        packet.check_len()?;
        Ok(packet)
    }

    /// Ensure that no accessor method will panic if called.
    /// Returns `Err(Error::Truncated)` if the buffer is too short.
    ///
    /// The result of this check is invalidated by calling [set_hardware_len] or
    /// [set_protocol_len].
    ///
    /// [set_hardware_len]: #method.set_hardware_len
    /// [set_protocol_len]: #method.set_protocol_len
    #[allow(clippy::if_same_then_else)]
    pub fn check_len(&self) -> Result<()> {
        let len = self.buffer.as_ref().len();
        if len < field::PWCON.end {
            Err(Error::Truncated)
        } else {
            Ok(())
        }
    }

    /// Consume the packet, returning the underlying buffer.
    pub fn into_inner(self) -> T {
        self.buffer
    }

    /// Return the identification field.
    pub fn identification(&self) -> &[u8] {
        let data = self.buffer.as_ref();
        &data[field::IDENT]
    }

    /// Return the password len field.
    pub fn password_len(&self) -> &[u8] {
        let data = self.buffer.as_ref();
        &data[field::PWLEN]
    }

    /// Return the password content field.
    pub fn password_content(&self) -> &[u8] {
        let data = self.buffer.as_ref();
        &data[field::PWCON]
    }
}

impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
    /// Set the identification field.
    pub fn set_identification(&mut self, value: &[u8]) {
        let data = self.buffer.as_mut();
        data[field::IDENT].copy_from_slice(value)
    }

    /// Set the password len field.
    pub fn set_password_len(&mut self, value: &[u8]) {
        let data = self.buffer.as_mut();
        data[field::PWLEN].copy_from_slice(value)
    }

    /// Set the password content field.
    pub fn set_password_content(&mut self, value: &[u8]) {
        let data = self.buffer.as_mut();
        data[field::PWCON].copy_from_slice(value)
    }
}

impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
    fn as_ref(&self) -> &[u8] {
        self.buffer.as_ref()
    }
}

// use crate::wire::{EthernetAddress, Ipv4Address};

#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Ident(pub [u8; 8]);

impl Ident {
    /// The zcore-rs ident.
    pub const ZCORE: Ident = Ident([0x7A, 0x63, 0x6F, 0x72, 0x65, 0x2D, 0x72, 0x73]);

    /// Construct an Ethernet address from a sequence of octets, in big-endian.
    ///
    /// # Panics
    /// The function panics if `data` is not six octets long.
    pub fn from_bytes(data: &[u8]) -> Ident {
        let mut bytes = [0; 8];
        bytes.copy_from_slice(data);
        Ident(bytes)
    }

    /// Return an Ethernet address as a sequence of octets, in big-endian.
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PassWordLen(pub [u8; 2]);

impl PassWordLen {
    pub const SHA256LEN: PassWordLen = PassWordLen([0x00, 0x20]);

    /// Construct an Ethernet address from a sequence of octets, in big-endian.
    ///
    /// # Panics
    /// The function panics if `data` is not six octets long.
    pub fn from_bytes(data: &[u8]) -> PassWordLen {
        let mut bytes = [0; 2];
        bytes.copy_from_slice(data);
        PassWordLen(bytes)
    }

    /// Return an Ethernet address as a sequence of octets, in big-endian.
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PassWord(pub [u8; 32]);

impl PassWord {
    /// Construct an Ethernet address from a sequence of octets, in big-endian.
    ///
    /// # Panics
    /// The function panics if `data` is not six octets long.
    pub fn from_bytes(data: &[u8]) -> PassWord {
        let mut bytes = [0; 32];
        bytes.copy_from_slice(data);
        PassWord(bytes)
    }

    /// Return an Ethernet address as a sequence of octets, in big-endian.
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

/// A high-level representation of an Address Resolution Protocol packet.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Repr {
    /// An Ethernet and IPv4 Address Resolution Protocol packet.
    EthernetIpv4 {
        identification: Ident,
        password_len: PassWordLen,
        password_content: PassWord,
    },
}

impl Repr {
    /// Parse an Address Resolution Protocol packet and return a high-level representation,
    /// or return `Err(Error::Unrecognized)` if the packet is not recognized.
    pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> {
        match true {
            true => Ok(Repr::EthernetIpv4 {
                identification: Ident::from_bytes(packet.identification()),
                password_len: PassWordLen::from_bytes(packet.password_len()),
                password_content: PassWord::from_bytes(packet.password_content()),
            }),
            _ => Err(Error::Unrecognized),
        }
    }

    /// Return the length of a packet that will be emitted from this high-level representation.
    pub fn buffer_len(&self) -> usize {
        match *self {
            Repr::EthernetIpv4 { .. } => field::PWCON.end,
        }
    }

    /// Emit a high-level representation into an Address Resolution Protocol packet.
    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
        match *self {
            Repr::EthernetIpv4 {
                identification,
                password_len,
                password_content,
            } => {
                packet.set_identification(identification.as_bytes());
                packet.set_password_len(password_len.as_bytes());
                packet.set_password_content(password_content.as_bytes());
            }
        }
    }
}

impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match Repr::parse(self) {
            Ok(repr) => write!(f, "{}", repr),
            _ => {
                write!(f, "Biu (unrecognized)")?;
                write!(
                    f,
                    " identification={:?} password_len={:?} password_content={:?} ",
                    self.identification(),
                    self.password_len(),
                    self.password_content()
                )?;
                Ok(())
            }
        }
    }
}

impl fmt::Display for Repr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Repr::EthernetIpv4 {
                identification,
                password_len,
                password_content,
            } => {
                write!(
                    f,
                    "Biu type=Ethernet+IPv4 ident={:?} password len = {:?} content={:?} ",
                    identification, password_len, password_content
                )
            }
        }
    }
}

use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};

impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
    fn pretty_print(
        buffer: &dyn AsRef<[u8]>,
        f: &mut fmt::Formatter,
        indent: &mut PrettyIndent,
    ) -> fmt::Result {
        match Packet::new_checked(buffer) {
            Err(err) => write!(f, "{}({})", indent, err),
            Ok(packet) => write!(f, "{}{}", indent, packet),
        }
    }
}

// #[cfg(test)]
// mod test {
//     use super::*;

//     static PACKET_BYTES: [u8; 28] =
//         [0x00, 0x01,
//          0x08, 0x00,
//          0x06,
//          0x04,
//          0x00, 0x01,
//          0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
//          0x21, 0x22, 0x23, 0x24,
//          0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
//          0x41, 0x42, 0x43, 0x44];

//     #[test]
//     fn test_deconstruct() {
//         let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
//         assert_eq!(packet.hardware_type(), Hardware::Ethernet);
//         assert_eq!(packet.protocol_type(), Protocol::Ipv4);
//         assert_eq!(packet.hardware_len(), 6);
//         assert_eq!(packet.protocol_len(), 4);
//         assert_eq!(packet.operation(), Operation::Request);
//         assert_eq!(packet.source_hardware_addr(), &[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]);
//         assert_eq!(packet.source_protocol_addr(), &[0x21, 0x22, 0x23, 0x24]);
//         assert_eq!(packet.target_hardware_addr(), &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]);
//         assert_eq!(packet.target_protocol_addr(), &[0x41, 0x42, 0x43, 0x44]);
//     }

//     #[test]
//     fn test_construct() {
//         let mut bytes = vec![0xa5; 28];
//         let mut packet = Packet::new_unchecked(&mut bytes);
//         packet.set_hardware_type(Hardware::Ethernet);
//         packet.set_protocol_type(Protocol::Ipv4);
//         packet.set_hardware_len(6);
//         packet.set_protocol_len(4);
//         packet.set_operation(Operation::Request);
//         packet.set_source_hardware_addr(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]);
//         packet.set_source_protocol_addr(&[0x21, 0x22, 0x23, 0x24]);
//         packet.set_target_hardware_addr(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]);
//         packet.set_target_protocol_addr(&[0x41, 0x42, 0x43, 0x44]);
//         assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
//     }

//     fn packet_repr() -> Repr {
//         Repr::EthernetIpv4 {
//             operation: Operation::Request,
//             source_hardware_addr:
//                 EthernetAddress::from_bytes(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]),
//             source_protocol_addr:
//                 Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
//             target_hardware_addr:
//                 EthernetAddress::from_bytes(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]),
//             target_protocol_addr:
//                 Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44])
//         }
//     }

//     #[test]
//     fn test_parse() {
//         let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
//         let repr = Repr::parse(&packet).unwrap();
//         assert_eq!(repr, packet_repr());
//     }

//     #[test]
//     fn test_emit() {
//         let mut bytes = vec![0xa5; 28];
//         let mut packet = Packet::new_unchecked(&mut bytes);
//         packet_repr().emit(&mut packet);
//         assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
//     }
// }
